/*
 * Copyright (C) 2009 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.smartandroid.sa.json;

import java.lang.reflect.Type;

/**
 * A holder class for an object and its type
 * 
 * @author Inderjeet Singh
 */
final class ObjectTypePair {
	private Object obj;
	final Type type;
	private final boolean preserveType;
	private final boolean systemOnly;

	ObjectTypePair(Object obj, Type type, boolean preserveType,
			boolean systemOnly) {
		this.obj = obj;
		this.type = type;
		this.preserveType = preserveType;
		this.systemOnly = systemOnly;
	}

	Object getObject() {
		return obj;
	}

	void setObject(Object obj) {
		this.obj = obj;
	}

	Type getType() {
		return type;
	}

	@Override
	public String toString() {
		return String.format("preserveType: %b, type: %s, obj: %s",
				preserveType, type, obj);
	}

	<HANDLER> Pair<HANDLER, ObjectTypePair> getMatchingHandler(
			ParameterizedTypeHandlerMap<HANDLER> handlers) {
		HANDLER handler = null;
		if (!preserveType && obj != null) {
			// First try looking up the handler for the actual type
			ObjectTypePair moreSpecificType = toMoreSpecificType();
			handler = handlers.getHandlerFor(moreSpecificType.type, systemOnly);
			if (handler != null) {
				return new Pair<HANDLER, ObjectTypePair>(handler,
						moreSpecificType);
			}
		}
		// Try the specified type
		handler = handlers.getHandlerFor(type, systemOnly);
		return handler == null ? null : new Pair<HANDLER, ObjectTypePair>(
				handler, this);
	}

	ObjectTypePair toMoreSpecificType() {
		if (preserveType || obj == null) {
			return this;
		}
		Type actualType = getActualTypeIfMoreSpecific(type, obj.getClass());
		if (actualType == type) {
			return this;
		}
		return new ObjectTypePair(obj, actualType, true, systemOnly);
	}

	Type getMoreSpecificType() {
		if (preserveType || obj == null) {
			return type;
		}
		return getActualTypeIfMoreSpecific(type, obj.getClass());
	}

	// This takes care of situations where the field was declared as an Object,
	// but the
	// actual value contains something more specific. See Issue 54.
	// TODO (inder): This solution will not work if the field is of a generic
	// type, but
	// the actual object is of a raw type (which is a sub-class of the generic
	// type).
	static Type getActualTypeIfMoreSpecific(Type type, Class<?> actualClass) {
		if (type instanceof Class<?>) {
			Class<?> typeAsClass = (Class<?>) type;
			if (typeAsClass.isAssignableFrom(actualClass)) {
				type = actualClass;
			}
			if (type == Object.class) {
				type = actualClass;
			}
		}
		return type;
	}

	@Override
	public int hashCode() {
		// Not using type.hashCode() since I am not sure if the subclasses of
		// type reimplement
		// hashCode() to be equal for equal types
		return ((obj == null) ? 31 : obj.hashCode());
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj == null) {
			return false;
		}
		if (getClass() != obj.getClass()) {
			return false;
		}
		ObjectTypePair other = (ObjectTypePair) obj;
		if (this.obj == null) {
			if (other.obj != null) {
				return false;
			}
		} else if (this.obj != other.obj) { // Checking for reference equality
			return false;
		}
		if (type == null) {
			if (other.type != null) {
				return false;
			}
		} else if (!type.equals(other.type)) {
			return false;
		}
		return preserveType == other.preserveType
				&& systemOnly == other.systemOnly;
	}

	public boolean isPreserveType() {
		return preserveType;
	}

	public boolean isSystemOnly() {
		return systemOnly;
	}
}
